Skip to content

LLM Based Task#2283

Open
KyleZheng1284 wants to merge 3 commits into
NVIDIA:mainfrom
KyleZheng1284:feature/llm-task-operator
Open

LLM Based Task#2283
KyleZheng1284 wants to merge 3 commits into
NVIDIA:mainfrom
KyleZheng1284:feature/llm-task-operator

Conversation

@KyleZheng1284

Copy link
Copy Markdown
Contributor

Description

  • Adds a reusable TextGenerationOperator built on the existing operator framework.
  • Introduces typed tasks for QA, summarization, and generic prompt generation.
  • Preserves existing QA APIs, schemas, prompts, and client compatibility.
  • Improves secure graph serialization without persisting API credentials.
  • Preserves sampling overrides and DataFrame value types across execution.
  • Keeps embedding and captioning as separate specialized operator families.
  • Adds tests for generation, persistence, validation, and backward compatibility

Checklist

  • I am familiar with the Contributing Guidelines.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.

Signed-off-by: Kyle Zheng <126034466+KyleZheng1284@users.noreply.github.com>
@KyleZheng1284 KyleZheng1284 requested review from a team as code owners June 30, 2026 05:30
@greptile-apps

greptile-apps Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a reusable TextGenerationOperator base and three typed task strategies (RagAnswerTask, SummarizeTask, GenericPromptTask) that unify QA, summarization, and generic prompt generation under a single operator framework, while preserving the existing flat QAGenerationOperator API for backward compatibility.

  • Task layer (models/llm/tasks/): Defines GenerationTask ABC with a strict invoke() lifecycle that sanitizes transport secrets, and a collecting execute() facade for batch operators; RagAnswerTask and SummarizeTask replace logic previously inlined in LiteLLMClient.generate().
  • Operator layer (operators/generation/): TextGenerationOperator adds concurrent row execution via ThreadPoolExecutor, positional result tracking, and safe graph serialization that strips live clients and tasks from persisted state.
  • Credential handling (common/params/models.py): Adds environment-reference support (os.environ/VAR) for api_key fields, recursive secret redaction in __repr__, and LLMSamplingOverrides for task-specific sampling defaults, plus graph serialization in graph_pipeline_registry.py bumped to format version 2.

Confidence Score: 4/5

The core operator logic and credential handling are well-tested and secure; one gap in RagAnswerTask prompt template validation means malformed templates silently fail at inference rather than at construction.

The RagAnswerTask custom prompt template is not validated at construction time: self.prompt.format(context=context, query=query) runs inside build_request, so an invalid placeholder like {typo} raises KeyError at inference, gets absorbed into request_error by the bare except Exception in invoke, and produces no diagnostic about the template. SummarizeTask correctly validates in post_init. Every other changed path — credential resolution, response shape validation, legacy adapter, sampling overrides, graph serialization — is solid and well-covered by the new test suite.

nemo_retriever/src/nemo_retriever/models/llm/tasks/rag_answer.py — missing post_init template validation for the custom prompt field.

Important Files Changed

Filename Overview
nemo_retriever/src/nemo_retriever/models/llm/tasks/rag_answer.py Encapsulates RAG prompt construction and reasoning controls into a typed task; custom prompt template is not validated at construction time, leading to silent request_error at inference for malformed templates.
nemo_retriever/src/nemo_retriever/models/llm/tasks/base.py New GenerationTask ABC with strict invoke() lifecycle and compatibility execute() facade; bare excepts in invoke() at _preflight_error, build_request, and parse phases swallow root exceptions without logging (previously flagged).
nemo_retriever/src/nemo_retriever/operators/generation/base.py New TextGenerationOperator with concurrent row execution, position-tracking, and safe graph serialization; bare except in _execute_row and _failure_model lack exc_info=True (previously flagged); process() logic is otherwise solid.
nemo_retriever/src/nemo_retriever/models/llm/tasks/generic.py New GenericPromptTask with thorough template validation; imports private _apply_reasoning_control from sibling rag_answer module, and uses string comparison for exception dispatch which is brittle.
nemo_retriever/src/nemo_retriever/common/params/models.py Adds TextGenerationParams, LLMSamplingOverrides, and environment-reference credential handling; secret redaction helpers are thorough; LLMRemoteClientParams now defers api_key resolution to worker side safely.
nemo_retriever/src/nemo_retriever/tools/evaluation/generation.py QAGenerationOperator refactored to extend TextGenerationOperator; backward-compatible flat constructor preserved; legacy LLMClient adapter correctly forwards reasoning_enabled; overwrite=True hardcoded for backward compatibility.
nemo_retriever/src/nemo_retriever/models/llm/clients/litellm.py Refactored to delegate to RagAnswerTask; new _call_complete validates response shape with UnsupportedTextResponseError; environment-reference api_key resolved at call time correctly.
nemo_retriever/src/nemo_retriever/graph/graph_pipeline_registry.py Significant expansion of graph serialization to version 2 with comprehensive secret field detection and environment-reference encoding; _encode_value covers Pydantic models, dicts, lists, and tuples safely.
nemo_retriever/src/nemo_retriever/models/llm/types.py Adds TextCompletionClient protocol, UnsupportedTextResponseError, GenerationRequest (with snapshot-on-construct immutability), and GeneratedTextResult; clean design with appropriate validation in post_init.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Op as TextGenerationOperator
    participant Pool as ThreadPoolExecutor
    participant Row as _execute_row
    participant Task as GenerationTask.invoke()
    participant Client as TextCompletionClient

    Op->>Pool: submit _execute_row(position, inputs) per row
    Pool->>Row: execute
    Row->>Task: _execute_task(inputs)
    Task->>Task: "_preflight_error(**inputs)"
    Task->>Task: "build_request(**inputs) → GenerationRequest"
    Task->>Client: complete(messages, max_tokens, extra_params)
    Client-->>Task: (raw_text, latency_s)
    Task->>Task: parse(raw_text) → text
    Task-->>Row: GeneratedTextResult
    Row-->>Pool: (position, result)
    Pool-->>Op: as_completed futures
    Op->>Op: "assert result_position == position"
    Op->>Op: write output columns to DataFrame copy
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Op as TextGenerationOperator
    participant Pool as ThreadPoolExecutor
    participant Row as _execute_row
    participant Task as GenerationTask.invoke()
    participant Client as TextCompletionClient

    Op->>Pool: submit _execute_row(position, inputs) per row
    Pool->>Row: execute
    Row->>Task: _execute_task(inputs)
    Task->>Task: "_preflight_error(**inputs)"
    Task->>Task: "build_request(**inputs) → GenerationRequest"
    Task->>Client: complete(messages, max_tokens, extra_params)
    Client-->>Task: (raw_text, latency_s)
    Task->>Task: parse(raw_text) → text
    Task-->>Row: GeneratedTextResult
    Row-->>Pool: (position, result)
    Pool-->>Op: as_completed futures
    Op->>Op: "assert result_position == position"
    Op->>Op: write output columns to DataFrame copy
Loading
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
nemo_retriever/src/nemo_retriever/models/llm/tasks/rag_answer.py:135-137
**Custom `prompt` template not validated at construction time**

`self.prompt.format(context=context, query=query)` runs at inference time without any prior validation of the template. If the template contains undeclared placeholders — e.g. `RagAnswerTask(prompt="{typo}")` — Python raises `KeyError: 'typo'` inside `build_request`. That exception is silently caught by `invoke`'s `build_request` handler and converted to `request_error` with no diagnostic message about the template, leaving every row in a batch failing with an opaque error.

`SummarizeTask` demonstrates the correct pattern: it calls `_summary_prompt_fields(self.prompt)` in `__post_init__` so an invalid template surfaces immediately at task construction. `RagAnswerTask` should do the same — validate at `__post_init__` that `self.prompt` uses only the `{context}` and `{query}` placeholders.

Reviews (3): Last reviewed commit: "Address generation review feedback" | Re-trigger Greptile

Comment thread nemo_retriever/src/nemo_retriever/operators/generation/base.py
Comment thread nemo_retriever/src/nemo_retriever/tools/evaluation/generation.py
Comment on lines +1 to +3
# SPDX-FileCopyrightText: Copyright (c) 2024-26, NVIDIA CORPORATION & AFFILIATES.
# All rights reserved.
# SPDX-License-Identifier: Apache-2.0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 SPDX copyright year format inconsistency across new files

The tasks/ package and operator files use three different year formats: 2024-26 (non-standard shorthand), 2024-25, and 2026 (standalone). The spdx-license-header rule requires the current year. New files in this PR use 2024-26 (tasks/__init__.py, tasks/base.py, tasks/generic.py, tasks/rag_answer.py, tasks/summarize.py) while test_generation_tasks.py uses 2026. The standard form in the rest of the repo is the four-digit year; the abbreviated 2024-26 is not conventional. All new files should use the same format as the existing codebase.

Prompt To Fix With AI
This is a comment left during a code review.
Path: nemo_retriever/src/nemo_retriever/models/llm/tasks/__init__.py
Line: 1-3

Comment:
**SPDX copyright year format inconsistency across new files**

The `tasks/` package and operator files use three different year formats: `2024-26` (non-standard shorthand), `2024-25`, and `2026` (standalone). The `spdx-license-header` rule requires the current year. New files in this PR use `2024-26` (`tasks/__init__.py`, `tasks/base.py`, `tasks/generic.py`, `tasks/rag_answer.py`, `tasks/summarize.py`) while `test_generation_tasks.py` uses `2026`. The standard form in the rest of the repo is the four-digit year; the abbreviated `2024-26` is not conventional. All new files should use the same format as the existing codebase.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@KyleZheng1284 KyleZheng1284 marked this pull request as draft June 30, 2026 17:26
@KyleZheng1284 KyleZheng1284 changed the title LLM Based Task Operator LLM Based Task Jun 30, 2026
Signed-off-by: Kyle Zheng <126034466+KyleZheng1284@users.noreply.github.com>
@KyleZheng1284 KyleZheng1284 marked this pull request as ready for review July 2, 2026 22:50
Comment thread nemo_retriever/src/nemo_retriever/operators/generation/base.py Outdated
Signed-off-by: Kyle Zheng <126034466+KyleZheng1284@users.noreply.github.com>
Comment on lines +135 to +137
if self.prompt is not None:
context = "\n\n---\n\n".join(chunks) if chunks else "(no context retrieved)"
messages[-1]["content"] = self.prompt.format(context=context, query=query)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Custom prompt template not validated at construction time

self.prompt.format(context=context, query=query) runs at inference time without any prior validation of the template. If the template contains undeclared placeholders — e.g. RagAnswerTask(prompt="{typo}") — Python raises KeyError: 'typo' inside build_request. That exception is silently caught by invoke's build_request handler and converted to request_error with no diagnostic message about the template, leaving every row in a batch failing with an opaque error.

SummarizeTask demonstrates the correct pattern: it calls _summary_prompt_fields(self.prompt) in __post_init__ so an invalid template surfaces immediately at task construction. RagAnswerTask should do the same — validate at __post_init__ that self.prompt uses only the {context} and {query} placeholders.

Prompt To Fix With AI
This is a comment left during a code review.
Path: nemo_retriever/src/nemo_retriever/models/llm/tasks/rag_answer.py
Line: 135-137

Comment:
**Custom `prompt` template not validated at construction time**

`self.prompt.format(context=context, query=query)` runs at inference time without any prior validation of the template. If the template contains undeclared placeholders — e.g. `RagAnswerTask(prompt="{typo}")` — Python raises `KeyError: 'typo'` inside `build_request`. That exception is silently caught by `invoke`'s `build_request` handler and converted to `request_error` with no diagnostic message about the template, leaving every row in a batch failing with an opaque error.

`SummarizeTask` demonstrates the correct pattern: it calls `_summary_prompt_fields(self.prompt)` in `__post_init__` so an invalid template surfaces immediately at task construction. `RagAnswerTask` should do the same — validate at `__post_init__` that `self.prompt` uses only the `{context}` and `{query}` placeholders.

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant